﻿/* 
* Copyright (c) 2010, The University of Texas at Austin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, 
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, 
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, 
* this list of conditions and the following disclaimer in the documentation and/or other materials 
* provided with the distribution.
*
* Neither the name of The University of Texas at Austin nor the names of its contributors may be 
* used to endorse or promote products derived from this software without specific prior written 
* permission.
* 
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;

namespace Bio.Views.Structure.Editor.Internal.Viewport
{
    public class DrawingViewportManager
    {
        public event EventHandler ViewportLocationChangedEvent;
        public event EventHandler ViewportSizeChangedEvent;

        public Rect ViewportBounds 
        { 
            get; 
            private set; 
        }

        /*public double ViewportLeft
        {
            get
            {
                if (ZoomFactor < 0.0)
                    return ViewportBounds.TopLeft.X;
                return (ViewportBounds.TopLeft.X / ZoomFactor);
            }
        }

        public double ViewportTop
        {
            get
            {
                if (ZoomFactor < 0.0)
                    return ViewportBounds.TopLeft.Y;
                return (ViewportBounds.TopLeft.Y / ZoomFactor);
            }
        }*/

        private double _zoomFactor;
        public double ZoomFactor 
        {
            get { return _zoomFactor; }
            set
            {
                if (value >= 0.0)
                {
                    _zoomFactor = value;
                    DetermineViewportSize();
                }
            }
        }

        private Size _drawingSize;
        public Size DrawingSize
        {
            get { return _drawingSize; }
            set
            {
                if (value.Height > 0.0 && value.Width > 0.0)
                {
                    _drawingSize = value;
                    DrawingBounds = new Rect(0, 0, _drawingSize.Width, _drawingSize.Height);
                    DetermineViewportSize();
                }
            }
        }

        public void FitDrawingToScreen(Size renderingSize)
        {
            ZoomFactor = Scroller.ActualHeight / renderingSize.Height;
        }

        public void ScrollViewportTo(Point location)
        {
            Point delta = new Point(location.X - ViewportBounds.TopLeft.X, location.Y - ViewportBounds.TopLeft.Y);
            ScrollViewportBy(delta);
        }

        public void ScrollViewportBy(Point delta)
        {
            Point newViewportTopLeft = new Point(ViewportBounds.TopLeft.X + delta.X, ViewportBounds.TopLeft.Y + delta.Y);
            
            if (newViewportTopLeft.X > MaxViewportTopLeft.X)
                newViewportTopLeft.X = MaxViewportTopLeft.X;
            else if (newViewportTopLeft.X < 0)
                newViewportTopLeft.X = 0;

            if (newViewportTopLeft.Y > MaxViewportTopLeft.Y)
                newViewportTopLeft.Y = MaxViewportTopLeft.Y;
            else if (newViewportTopLeft.Y < 0)
                newViewportTopLeft.Y = 0;

            //Triggers the ScrollChanged Event.
            if (ZoomFactor != 0)
            {
                Scroller.ScrollToVerticalOffset(newViewportTopLeft.Y * ZoomFactor);
                Scroller.ScrollToHorizontalOffset(newViewportTopLeft.X * ZoomFactor);
            }
            else
            {
                Scroller.ScrollToVerticalOffset(newViewportTopLeft.Y);
                Scroller.ScrollToHorizontalOffset(newViewportTopLeft.X);
            }
        }

        public void CenterViewportAt(Point location)
        {
            if (!DrawingBounds.Contains(location))
                return;
            
            //We will move the viewport so that the desired location is as centered as possible.
            Point viewportTargetTopLeft = new Point((location.X - Math.Round(ViewportBounds.Width / 2.0)), 
                (location.Y - Math.Round(ViewportBounds.Height / 2.0)));

            double viewportOffsetX = 0.0;
            double viewportOffsetY = 0.0;

            if (viewportTargetTopLeft.X < 0.0)
                viewportOffsetX = 0.0;
            else if (viewportTargetTopLeft.X > MaxViewportTopLeft.X)
                viewportOffsetX = MaxViewportTopLeft.X;
            else
                viewportOffsetX = viewportTargetTopLeft.X;

            if (viewportTargetTopLeft.Y < 0.0)
                viewportOffsetY = 0.0;
            else if (viewportTargetTopLeft.Y > MaxViewportTopLeft.Y)
                viewportOffsetY = MaxViewportTopLeft.Y;
            else
                viewportOffsetY = viewportTargetTopLeft.Y;

            //Triggers the ScrollChanged Event.
            if (ZoomFactor != 0)
            {
                Scroller.ScrollToHorizontalOffset(viewportOffsetX * ZoomFactor);
                Scroller.ScrollToVerticalOffset(viewportOffsetY * ZoomFactor);
            }
            else
            {
                Scroller.ScrollToHorizontalOffset(viewportOffsetX);
                Scroller.ScrollToVerticalOffset(viewportOffsetY);
            }
        }

        public DrawingViewportManager(ScrollViewer scroller, double zoomFactor, Size drawingSize)
        {
            Debug.Assert(scroller != null);
            Scroller = scroller;
            Scroller.ScrollChanged += new ScrollChangedEventHandler(OnScrollerScrollChanged);
            DrawingSize = drawingSize;
        }

        private ScrollViewer Scroller { get; set; }
        private Point MaxViewportTopLeft { get; set; }
        private Rect DrawingBounds { get; set; }
        private Point CurrentPanStartPoint { get; set; }

        private void DetermineViewportSize()
        {
            Size viewportSize = (ZoomFactor != 0) ? new Size(Scroller.ViewportWidth / ZoomFactor, Scroller.ViewportHeight / ZoomFactor) :
                new Size(Scroller.ViewportWidth, Scroller.ViewportHeight);

            if (viewportSize.Width > DrawingSize.Width)
                viewportSize = new Size(DrawingSize.Width, viewportSize.Height);
            if (viewportSize.Height > DrawingSize.Height)
                viewportSize = new Size(viewportSize.Width, DrawingSize.Height);

            bool viewportSizeChanged = (viewportSize.Width != ViewportBounds.Width || viewportSize.Height != ViewportBounds.Height);

            ViewportBounds = (ZoomFactor != 0) ? new Rect(Scroller.HorizontalOffset / ZoomFactor, Scroller.VerticalOffset / ZoomFactor, viewportSize.Width, viewportSize.Height) :
                new Rect(Scroller.HorizontalOffset, Scroller.VerticalOffset, viewportSize.Width, viewportSize.Height);

            //ViewportBounds = new Rect(Scroller.HorizontalOffset, Scroller.VerticalOffset, viewportSize.Width, viewportSize.Height);

            MaxViewportTopLeft = (ZoomFactor != 0) ? new Point(Scroller.ExtentWidth / ZoomFactor - ViewportBounds.Width, Scroller.ExtentHeight / ZoomFactor - ViewportBounds.Height) :
                        new Point(Scroller.ExtentWidth - ViewportBounds.Width, Scroller.ExtentHeight - ViewportBounds.Height);

            if (viewportSizeChanged && ViewportSizeChangedEvent != null)
                ViewportSizeChangedEvent(this, EventArgs.Empty);
        }

        private void OnScrollerScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            DetermineViewportSize();
            if (ViewportLocationChangedEvent != null)
                ViewportLocationChangedEvent(this, EventArgs.Empty);
        }
    }
}
